﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;

namespace GempGame
{
    public enum Object
    {
        EMPTY,
        PLAYER,
        BUILDING
    }

    #region Node class
    class Node : IComparable
    {
        public Node Parent;
        public Vector2 Position;

        public float g_cost = 0;
        public float h_cost = 0;

        public Node(Vector2 position)
        {
            this.Position = position;
            this.Parent = null;
        }

        public Node(Vector2 position, Node parent)
        {
            this.Position = position;
            this.Parent = parent;
        }

        public List<Node> getSuccessors(Object[,] grid, int gridColumns, int gridRows)
        {
            List<Node> successors = new List<Node>();

            for (int i = -1; i <= 1; i++)
            {
                for (int j = -1; j <= 1; j++)
                {
                    if (i != 0 || j != 0)
                    {
                        int yIndex = (int)this.Position.Y + i;
                        int xIndex = (int)this.Position.X + j;

                        xIndex = (xIndex < 0) ? 0 : xIndex;
                        yIndex = (yIndex < 0) ? 0 : yIndex;

                        xIndex = (xIndex >= gridColumns) ? gridColumns-1 : xIndex;
                        yIndex = (yIndex >= gridRows) ? gridRows-1 : yIndex;

                        if (grid[yIndex, xIndex] != Object.BUILDING)
                        {
                            successors.Add(new Node(
                                new Vector2(xIndex, yIndex),
                                this)
                            );
                        }
                    }
                }
            }

            return successors;
        }

        public int CompareTo(object obj)
        {
            if (!(obj is Node))
            {
                throw new ArgumentException
                   ("Bad Comparison!");
            }

            Node node = (Node)obj;

            //Console.WriteLine("compareTo: " + (g_cost + h_cost) + " vs " + (node.g_cost + node.h_cost) + " = " + (g_cost + h_cost).CompareTo((node.g_cost + node.h_cost)));

            return (node.g_cost + node.h_cost).CompareTo((g_cost + h_cost));
        }

        public override bool Equals(object obj)
        {
            if (obj == null) return false;

            if (this.GetType() != obj.GetType()) return false;

            // safe because of the GetType check
            Node node = (Node)obj;

            // use this pattern to compare value members
            if (!this.Position.Equals(node.Position)) return false;

            return true;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
    #endregion

    #region Grid class
    public class Grid
    {

        public int tileWidth;
        public int tileHeight;

        int columns = 32;
        int rows = 32;


        Object[,] grid;

        //buildings in the grid with original dimensions
        List<Rectangle> buildings;

        //buildings in the grid with the grid dimensions
        List<Rectangle> gridBuildings;
        
        public Grid(int windowWidth, int windowHeight)
        {
            Console.WriteLine("WIDTH: " + windowWidth + " / HEIGHT: " + windowHeight);

            tileWidth = windowWidth / columns;
            tileHeight = windowHeight / rows;

            Console.WriteLine("tileWidth: " + tileWidth + " / tileHeight: " + tileHeight);

            grid = new Object[rows, columns];
            buildings = new List<Rectangle>();
            gridBuildings = new List<Rectangle>();
            
            //Console.WriteLine("the grid:");
            for(int i=0; i<rows; i++)
            {
                for (int j = 0; j < columns; j++)
                {
                    grid[i,j] = Object.EMPTY;
                    //Console.Write(j+i*columns + " . ");
                }
                //Console.WriteLine();
            }
        }


        public void addObject(Rectangle buildingRect)
        {
            bool intersected = false;
            int top = -1;
            int bottom = -1;
            int left = -1;
            int right = -1;

            for (int i = 0; i < rows; i++)
            {
                intersected = false;
                for (int j = 0; j < columns; j++)
                {
                    if (buildingRect.Intersects(new Rectangle(j * tileWidth, i * tileHeight, tileWidth, tileHeight)))
                    {
                        intersected = true;

                        if (left == -1)
                            left = j;
                        if (j > right)
                            right = j;

                        grid[i, j] = Object.BUILDING;

                        Console.WriteLine("added building to " + j + "/" + i);
                    }
                }
                if (intersected)
                {
                    if (top == -1)
                        top = i;
                    if (i > bottom)
                        bottom = i;
                }
            }

            buildings.Add(buildingRect);

            Rectangle gridRect = new Rectangle(left, top, (right - left + 1), (bottom - top + 1));
            Console.WriteLine(gridRect);
            gridBuildings.Add(gridRect);
        }

        public List<Vector2> findPath(Vector2 playerPos, Vector2 mousePos)
        {
            Vector2 source = 
                new Vector2(
                    (int) playerPos.X / tileWidth, 
                    (int) playerPos.Y / tileHeight);
            Console.WriteLine("source: " + source);

            mousePos.X /= tileWidth;
            mousePos.Y /= tileHeight;

            //verifica se mousepos ta dentro de um edificio
            foreach (Rectangle building in gridBuildings)
            {
                //Console.Write(building + " vs " + new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1) + " || ");
                //Console.WriteLine(building.Intersects(new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1)));

                //Console.WriteLine(building.Left + " , " + building.Right + " , " + building.Top + " , " + building.Bottom);

                //se estiver...
                if (building.Intersects(new Rectangle((int)mousePos.X, (int)mousePos.Y, 1, 1)))
                {
                    //descobre lado para o qual o jogador está direcionado
                    int diffx = (int)(mousePos.X - source.X);
                    int diffy = (int)(mousePos.Y - source.Y);

                    //xx
                    if (Math.Abs(diffx) > Math.Abs(diffy))
                    {
                        if (diffx > 0)
                        {
                            //Console.WriteLine("#1");

                            //esquerda do rectangulo
                            mousePos.X = building.Left - 1;
                            mousePos.Y = building.Top + building.Height / 2;
                        }
                        else
                        {
                            //Console.WriteLine("#2: " + building.Right + " + " + 1);

                            //direita do rectangulo
                            mousePos.X = building.Right;
                            mousePos.Y = building.Top + building.Height / 2;
                        }
                    }

                    //yy
                    else
                    {
                        if (diffy > 0)
                        {
                            //Console.WriteLine("#3: "+building.Top + " - " +1);

                            //acima do rectangulo
                            mousePos.Y = building.Top - 1;
                            mousePos.X = building.Left + building.Width / 2;
                        }
                        else
                        {
                            //Console.WriteLine("#4");

                            //abaixo do rectangulo
                            mousePos.Y = building.Bottom;
                            mousePos.X = building.Left + building.Width / 2;
                        }
                    }

                    break;
                }
            }

            Vector2 target =
                new Vector2(
                    (int) mousePos.X ,
                    (int) mousePos.Y );
            Console.WriteLine("target: " + target);

            if (source.Equals(target))
            {
                return new List<Vector2>();
            }

            return aStar(source, target);
        }


        // a* algorythm
        private List<Vector2> aStar(Vector2 source, Vector2 target)
        {
            List<Vector2> SolutionPathList = new List<Vector2>();

            List<Node> openList = new List<Node>();
            List<Node> closedList = new List<Node>();

            Node start_node = new Node(source);
            start_node.h_cost = Vector2.Distance(start_node.Position, target);

            Node target_node = new Node(target);

            openList.Add(start_node);


            //Console.WriteLine("start_node: " + start_node.Position);

            //main loop
            while (openList.Count > 0)
            {
                // pop
                Node currentNode = getLowestFvalueNode(openList) ;

                //Console.WriteLine("current_node: " + currentNode.Position + ", f="+currentNode.h_cost+currentNode.g_cost);

                // match?
                if (Vector2.Distance(currentNode.Position, target) < 1)
                {
                    //Console.WriteLine("i found it");
                    target_node.Parent = currentNode.Parent;
                    break;
                }

                //remove current node from open list
                openList.Remove(currentNode);

                //add current node to the closed list
                closedList.Add(currentNode);

                List<Node> successors = currentNode.getSuccessors(grid, columns, rows);
                foreach (Node successor_node in successors)
                {
                    //Console.WriteLine(" .successor: " + successor_node.Position);

                    // set sucessor_node's g_cost
                    successor_node.g_cost = currentNode.g_cost + 1;

                    // if sucessor_node exists in the closed list but the existing one is better, discard this successor
                    int cIndex = closedList.IndexOf(successor_node);
                    if (cIndex >= 0)
                    {
                        Node existing_node = closedList.ElementAt(cIndex);
                        if (existing_node.CompareTo(currentNode) <= 0)
                            continue;
                    }

                    // if sucessor_node exists in the open list but the existing one is better, discard this successor
                    int oIndex = openList.IndexOf(successor_node);
                    if (oIndex >= 0)
                    {
                        Node existing_node = openList.ElementAt(oIndex);
                        if (existing_node.CompareTo(currentNode) <= 0)
                            continue;
                    }

                    //remove the successor_node existing in the open and closed lists
                    if (oIndex != -1)
                        openList.RemoveAt(oIndex);
                    if (cIndex != -1)
                        closedList.RemoveAt(cIndex);

                    //successor_node's parent is the current node
                    successor_node.Parent = currentNode;

                    //add the estimate cost to the successor_node
                    successor_node.h_cost = Vector2.Distance(successor_node.Position, target);

                    //add the successor_node the open list
                    openList.Add(successor_node);
                }
            }

            //Console.WriteLine("fim do loop");

            //get the solution path
            while (target_node != null)
            {
                SolutionPathList.Insert(0, target_node.Position);
                target_node = target_node.Parent;
            }

            return SolutionPathList;
        }

        private Node getLowestFvalueNode(List<Node> openList)
        {
            Node lowcost_node = new Node(new Vector2(-1,-1));
            lowcost_node.g_cost = Int16.MaxValue;

            foreach (Node n in openList)
            {
                if (lowcost_node.CompareTo(n) <= 0)
                {
                    lowcost_node = n;
                }
            }
            return lowcost_node;
        }
        
    }
    #endregion
}
